package k_singleton.a_design_pattern.c_double_checked_locking;

/**
 * Created by wug on 2016/7/15 0015.
 * 双重校验锁
 */
public class Singleton {
//    private static Singleton instance;
    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        //Single Checked
        if (instance == null) {
            // 使用了同步块的方式
            synchronized (Singleton.class) {
                //Double Checked
                if (instance == null) {
                    try {
                        Thread.sleep((long)(Math.random() * 100L));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    /**
     这段代码看起来很完美，很可惜，它是有问题。主要在于instance = new Singleton()这句，这并非是一个原子操作!!!
     事实上在 JVM 中这句话大概做了下面 3 件事情。

     1.给 instance 分配内存
     2.调用 Singleton 的构造函数来初始化成员变量
     3.将instance对象指向分配的内存空间（执行完这步 instance 就为非 null 了）
     但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的.
     最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者，则在 3 执行完毕、2 未执行之前，被线程二抢占了，
     这时 instance 已经是非 null 了（但却没有初始化），所以线程二会直接返回 instance，然后使用，然后顺理成章地报错。
     （这里我实在写不出来demo测试这总情况了，水平有限哇，欢迎提供demo）

     出现这种情况我们只需要将 instance 变量声明成 volatile 就可以了。


     使用 volatile 的主要原因是其另一个特性：禁止指令重排序优化。
     也就是说，在 volatile 变量的赋值操作后面会有一个内存屏障（生成的汇编代码上），
     读操作不会被重排序到内存屏障之前。
     比如上面的例子，取操作必须在执行完 1-2-3 之后或者 1-3-2 之后，不存在执行到 1-3 然后取到值的情况。
     从「先行发生原则」的角度理解的话，就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作（这里的“后面”是时间上的先后顺序）。

     但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。
     其原因是 Java 5 以前的 JMM （Java 内存模型）是存在缺陷的，即时将变量声明成 volatile 也不能完全避免重排序，
     主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复，
     所以在这之后才可以放心使用 volatile。

     参考 http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/


     (具体怎么测出不使用volatile的问题 多线程水平有限 欢迎提供demo!!! （这里暂时说模式）)
     */

}
